#include <pic.h>

// Typedefs
typedef unsigned char bit_t;      // 1 bit
typedef unsigned char byte_t;     // 8 bits
typedef unsigned int word_t;      // 16 bits

typedef unsigned int frac_t;      // encoded *10 

void delay_us()
{
    unsigned char _dcnt;
	_dcnt = 1;
    while(--_dcnt != 0) {}
}

void delay_ms(unsigned int ms)
{
	byte_t cnt1 = 0x85;
	byte_t cnt2 = 0xB0;
	do
	{
		do {} while(--cnt1);
	} while (--cnt2);
}

bit i2c_timeout = 0;
byte_t i2c_timer = 0;

#if 0

#define i2c_wait(x) do { if (!i2c_timeout) { i2c_timer = 255; while (x) { if (i2c_ping()) break; } } } while (0)

bit_t i2c_ping()
{
	i2c_timer--;
	if (i2c_timer == 0)
	{
		i2c_timeout = 1;
		return 1;
	}
	return 0;
}

#else

#define i2c_wait(x) do {} while (x)

#endif

void i2c_send_start()
{
	i2c_timeout = 0;
	SEN = 1;
	i2c_wait(SEN);
}

void i2c_send_repeated_start()
{
	if (i2c_timeout)
		return;
	RSEN = 1;
	i2c_wait(RSEN);
}

void i2c_send_stop()
{
	if (i2c_timeout)
		return;
	PEN = 1;
	i2c_wait(PEN);
}

void i2c_send_byte(byte_t b)
{
	if (i2c_timeout)
		return;
	SSPBUF = b;
	i2c_wait(SSPCON2 & 0x1f);
	i2c_wait(RW);
	i2c_wait(ACKSTAT);
}

byte_t i2c_get_byte_ack()
{
	byte_t b;
	if (i2c_timeout)
		return 0;
	RCEN = 1;
	i2c_wait(RCEN);
	b = SSPBUF;
	ACKDT = 0;
	ACKEN = 1;
	i2c_wait(ACKEN);
	return b;
}

byte_t i2c_get_byte_nack()
{
	byte_t b;
	if (i2c_timeout)
		return 0;
	RCEN = 1;
	i2c_wait(RCEN);
	b = SSPBUF;
	ACKDT = 1;
	ACKEN = 1;
	i2c_wait(ACKEN);
	return b;
}

void write_led(int on)
{
	delay_us();
	PORTC &= ~(1<<6);
	delay_us();
	if (on)
		PORTC |= 1<<7;
	else
		PORTC &= ~(1<<7);
	delay_us();
	PORTC |= 1<<6;
	delay_us();
	PORTC &= ~(1<<6);
	delay_us();
}

void cycle_sequencer()
{
	PORTB |= 0x2;
	delay_us();
	PORTB &= ~0x2;
}

#define NUM_BAR_LEDS 7

frac_t baby_temp_led_table[NUM_BAR_LEDS+1] = { 0x0, 360, 365, 368, 372, 375, 380, 0xffff };

frac_t humidity_led_table[NUM_BAR_LEDS+1] = { 0x0, 600, 700, 733, 766, 800, 900, 0xffff };

const frac_t baby_temp_alarm[2] = { 360, 380 };
const frac_t air_temp_alarm[2] = { 340, 390 };
const frac_t humidity_alarm[2] = { 600, 900 };

bit baby_temp_alarm_active = 0;
bit air_temp_alarm_active = 0;
bit humidity_alarm_active = 0;

byte_t baby_temp_alarm_flash = 0;
byte_t air_temp_alarm_flash = 0;
byte_t humidity_alarm_flash = 0;

bit global_alarm_active = 0;

byte_t global_alarm_period = 0;
byte_t global_alarm_period_count = 0;
byte_t global_alarm_timer = 0;

bit alarm_disable_button_was_pressed = 0;
bit alarm_disable_button_pressed = 0;
word_t alarm_disable_timer = 0;
bit alarm_disable = 0;

frac_t thermostat_temp = 375;

bit thermostat_up_pressed = 0;
bit thermostat_up_was_pressed = 0;
bit thermostat_down_pressed = 0;
bit thermostat_down_was_pressed = 0;

word_t thermostat_display_timer = 0;

bit heater_on;

word_t alarm_oscillator = 0;
word_t warning_oscillator = 0;

word_t timer = 0;

bit power_on_pressed = 0;
bit power_on_was_pressed = 0;

bit power_on;

frac_t air_temp = 375;
frac_t baby_temp = 370;
frac_t humidity = 750;

bit air_temp_broken;
bit baby_temp_broken;
bit humidity_broken;

int ui_pacer;

void clear_leds(byte_t num)
{
	do {
		write_led(0);
	} while (--num);
}

void clear_display()
{
	clear_leds(80);
	cycle_sequencer();
}

// 7 segment LCD display codes
//
//  122222223
//  1       3
//  1       3
//  000000000
//  7       5
//  7       5
//  766666665  4
//
const byte_t digit_code[11] =
{
    0B11101110, // 0
    0B00101000, // 1
    0B11001101, // 2
    0B01101101, // 3
    0B00101011, // 4
    0B01100111, // 5
    0B11100111, // 6
    0B00101100, // 7
    0B11101111, // 8
    0B01101111, // 9
	0B00000000, // nothing
};

void display_digit(byte_t digit, bit_t dec)
{
	int code;
	if (digit > 10)
		digit = 10;
	code = digit_code[digit];
	write_led((code>>0)&1);
	write_led((code>>1)&1);
	write_led((code>>2)&1);
	write_led((code>>3)&1);
	write_led(dec);
	write_led((code>>5)&1);
	write_led((code>>6)&1);
	write_led((code>>7)&1);
}

void display_number(frac_t value, bit_t decimal, bit_t mask)
{
   byte_t tenths = 0, ones = 0, tens = 0;
   while (value >= 100)
   {
      value -= 100;
      tens++;
   }
   while (value >= 10)
   {
      value -= 10;
      ones++;
   }
   tenths = value;

   if (mask)
   {
		tenths = 10;
		ones = 10;
		tens = 10;
   }

   if (decimal)
      display_digit(tenths, 0);
   display_digit(ones, decimal);
   display_digit(tens, 0);
}

void display_bar(frac_t value, frac_t* tbl)
{
   byte_t i = NUM_BAR_LEDS-1;
   do {
	  bit_t lit = value >= tbl[i] && value < tbl[i+1];
      if (i == 0 || i == 6) lit &= alarm_oscillator;
	  if (i == 1 || i == 5) lit &= warning_oscillator;
 	  write_led(lit);
   } while (i--);
}

void read_sensors()
{
	byte_t lo, hi, i;

	if (!baby_temp_broken)
	{
		i2c_send_start();
		i2c_send_byte(0b10011100);
		i2c_send_byte(0b00000000);
		i2c_send_repeated_start();
		i2c_send_byte(0b10011101);
		hi = i2c_get_byte_ack();
		lo = i2c_get_byte_nack();
		i2c_send_stop();
		
		baby_temp = hi*10 + lo*10/256;
	}

	if (!air_temp_broken)
	{
		i2c_send_start();
		i2c_send_byte(0b10011110);
		i2c_send_byte(0b00000000);
		i2c_send_repeated_start();
		i2c_send_byte(0b10011111);
		hi = i2c_get_byte_ack();
		lo = i2c_get_byte_nack();
		i2c_send_stop();
		
		air_temp = hi*10 + lo*10/256;
	}

	if (!humidity_broken)
	{
		// Humidity
		TMR1H = 0;
		TMR1L = 0;
		T1CON |= 1;
		delay_ms(135);
		T1CON &= ~1;

    	humidity = ((0x50C - (TMR1H << 8 | TMR1L)) >> 1) * 10;
		TMR1H = 0;
		TMR1L = 0;
	}
}

void read_buttons()
{
	alarm_disable_button_was_pressed = alarm_disable_button_pressed;
	alarm_disable_button_pressed = !(PORTB & 0b00100000);

	power_on_was_pressed = power_on_pressed;
	power_on_pressed = !(PORTB & 0b00010000);

	thermostat_up_was_pressed = thermostat_up_pressed;
	thermostat_up_pressed = !(PORTB & 0b00001000);

	thermostat_down_was_pressed = thermostat_down_pressed;
	thermostat_down_pressed = !(PORTB & 0b00000100);
}

void display_sensors()
{
	byte_t alarm_led;

    // Displays are serialized in the order they are wired.
    display_bar(humidity, humidity_led_table);

	// Global alarm led
	alarm_led = 0;
	if (alarm_disable_timer > 0)
	{
		// Fast flashing at the end of the disabled period.
		if (alarm_disable_timer <= 32 && ((alarm_disable_timer & 3) == 0))
			alarm_led = 1;
		// Regular flashing while alarms disabled.
		if (alarm_disable_timer > 32 && ((alarm_disable_timer & 31) == 0))
			alarm_led = 1;
	}
	else if (global_alarm_active & alarm_oscillator)
			alarm_led = 1;

    write_led(alarm_led);

	if (thermostat_display_timer > 0)
	{
		thermostat_display_timer--;
	    display_number(thermostat_temp, 1, air_temp_alarm_flash&1);
	}
	else
	    display_number(air_temp, 1, air_temp_alarm_flash&1);

    display_bar(baby_temp, baby_temp_led_table);

    write_led(heater_on);

	display_number(humidity, 0, humidity_alarm_flash&1);
 
    display_number(baby_temp, 1, baby_temp_alarm_flash&1);

	cycle_sequencer();
}

void check_alarms()
{
	bit_t global_alarm_was_active;
	
	byte_t num_alarms_active;
	byte_t alarms_active[3];

	if (alarm_disable_button_pressed && !alarm_disable_button_was_pressed)
		alarm_disable_timer = 256;
	if (alarm_disable_timer > 0)
	{
		alarm_disable_timer--;
		alarm_disable = 1;
	}
	else
		alarm_disable = 0;

	global_alarm_was_active = global_alarm_active;
	global_alarm_active = 0;
	num_alarms_active = 0;

	air_temp_alarm_active = 0;
	baby_temp_alarm_active = 0;
	humidity_alarm_active = 0;

	if (!alarm_disable)
	{
//		if (air_temp < air_temp_alarm[0] || air_temp > air_temp_alarm[1])
		{
			air_temp_alarm_active = 1;
			global_alarm_active = 1;
			alarms_active[num_alarms_active++] = 0;
		}
	
//		if (baby_temp < baby_temp_alarm[0] || baby_temp > baby_temp_alarm[1])
		{
			baby_temp_alarm_active = 1;
			global_alarm_active = 1;
			alarms_active[num_alarms_active++] = 1;
		}
	
//		if (humidity < humidity_alarm[0] || humidity > humidity_alarm[1])
		{
			humidity_alarm_active = 1;
			global_alarm_active = 1;
			alarms_active[num_alarms_active++] = 2;
		}
	}

	// For Prescale 16: Convert Hz to PR2: (62500 / freq) - 1 = PR2
	// For Prescale 4: Convert Hz to PR2: (250000 / freq) - 1 = PR2
	// Set CCPR1L to PR2/2.
#if 0  // Single tone w/ delay pattern 30 23 16 9 2
	air_temp_alarm_flash = air_temp_alarm_active & alarm_oscillator;
	baby_temp_alarm_flash = baby_temp_alarm_active & alarm_oscillator;
	humidity_alarm_flash = humidity_alarm_active & alarm_oscillator;

	CCP1CON = 0x0;	  // turn buzzer off
	if (global_alarm_active)
	{
		if (global_alarm_active && !global_alarm_was_active)
		{
			global_alarm_period = 30;
			global_alarm_period_count = 0;
			global_alarm_timer = 0;
		}
		if (global_alarm_timer == 0)
		{
			PR2 = 59;
			CCPR1L = 59/2;
			CCP1CON = 0x0f;	  // turn buzzer on
			global_alarm_timer = global_alarm_period;
			if (global_alarm_period > 7)
				global_alarm_period -= 7;
		}

		global_alarm_timer--;
	}
#else // Different tone per alarm, in sequence, with delay pattern.
	if (air_temp_alarm_flash > 0) air_temp_alarm_flash--;
	if (baby_temp_alarm_flash > 0) baby_temp_alarm_flash--;
	if (humidity_alarm_flash > 0) humidity_alarm_flash--;

	CCP1CON = 0x0;	  // turn buzzer off
	if (global_alarm_active)
	{
		if (global_alarm_active && !global_alarm_was_active)
		{
			global_alarm_period = 20;
			global_alarm_period_count = 0;
			global_alarm_timer = 0;
		}
		if (global_alarm_timer == 0)
		{
			byte_t alarm_mod;
			byte_t flash_amt;
			if (global_alarm_period > 6)
				flash_amt = 3;
			else
				flash_amt = 1;

			if (global_alarm_period_count >= 6)
			{
				if (global_alarm_period > 2)
					global_alarm_period -= 6;
				global_alarm_period_count = 0;
			}

			alarm_mod = global_alarm_period_count;
			while (alarm_mod >= num_alarms_active)
				alarm_mod -= num_alarms_active;
			alarm_mod = alarms_active[alarm_mod];
			if (alarm_mod == 0)
			{
				air_temp_alarm_flash = flash_amt;
				PR2 = 39;
				CCPR1L = 39/2;
			}
			if (alarm_mod == 1)
			{
				baby_temp_alarm_flash = flash_amt;
				PR2 = 46;
				CCPR1L = 46/2;
			}
			if (alarm_mod == 2)
			{
				humidity_alarm_flash = flash_amt;
				PR2 = 59;
				CCPR1L = 59/2;
			}
			CCP1CON = 0x0f;	  // turn buzzer on

			global_alarm_period_count++;
			global_alarm_timer = global_alarm_period;
		}
		global_alarm_timer--;
	}
#endif
}

void run_heater()
{
   heater_on = air_temp < thermostat_temp;

	if (heater_on)
		PORTE |= 1;
	else
		PORTE &= ~1;

	if (thermostat_up_pressed && !thermostat_up_was_pressed)
	{
		thermostat_display_timer = 30;
		thermostat_temp += 1;
	}
	if (thermostat_down_pressed && !thermostat_down_was_pressed)
	{
		thermostat_display_timer = 30;
		thermostat_temp -= 1;
	}
}

void incubator_main()
{
	clear_display();

   for (;;)
   {
	  timer++;
	  alarm_oscillator = (timer)&1;
	  warning_oscillator = (timer>>1)&1;

	  read_buttons();

	  if (power_on)
	  {
      	read_sensors();

      	display_sensors();

      	check_alarms();

	  	run_heater();

		if (power_on_pressed && !power_on_was_pressed)
		{
			power_on = 0;
			clear_display();
			CCP1CON = 0x0;	  // turn buzzer off
			PORTE &= ~1;
			alarm_disable_timer = 0;
			thermostat_display_timer = 0;
			baby_temp_broken = 1;
			alarm_disable_timer = 256;
			thermostat_display_timer = 30;
		}
	  }
	  else
      {
		if (power_on_pressed && !power_on_was_pressed)
		{
			power_on = 1;
		}
      }

		while (ui_pacer < 10) {}
		ui_pacer = 0;
	}
}

static void interrupt int_service(void)
{
	++ui_pacer;
	T0IF = 0;  // Clear interrupt flag, ready for next
}

void music()
{
	for (;;)
	{
		// 1,000,000 cycles/sec, 2 cycles/loop = 500,000 loops/sec
		// A4 = 880hz, 500,000/880=568, rise and fall so 568/2 = 284
		byte_t cnt1;
		PORTC |= 4; 
		cnt1 = 255;	while (--cnt1 != 0) { }
		cnt1 = 29;  while (--cnt1 != 0) { }
		PORTC &= ~4;
		cnt1 = 255; while (--cnt1 != 0) { }
		cnt1 = 29;  while (--cnt1 != 0) { }
	}
}

void init(void)
{
	power_on = 1;
	baby_temp_broken = 1;
	alarm_disable_timer = 256;
	thermostat_display_timer = 30;

	OPTION = 0x05;
	T0IE = 1;  // Enable interrupt on TMR0 overflow
	GIE = 1;  // Global interrupt enable

	PORTE &= ~1;

	T1CON = 0xf;	// turn on timer 1 to real time clock - start here for timer "warm-up"
	//T2CON = 0x5;  // postscale 1:1, prescaler 4, timer2 ON
	T2CON = 0x7;  // postscale 1:1, prescaler 16, timer2 ON

	TRISB |= 0b00111100;
	TRISC &= ~((1<<2)|(1<<6)|(1<<7));

	OPTION &= ~(1<<7);

	TRISE &= ~(0x1<<0);
	TRISB &= ~(0x1<<1);
//	TRISA |= (0x1<<4);

	TRISC |= 0b00011000;
	SSPCON = 0b00101000;
	SMP = 1;
	SSPADD = 20;

/*
	i2c_send_start();
	i2c_send_byte(0b10011100);  // Send TC75 ADDRESS (write)
	i2c_send_byte(0b00000001);  // Config address
	i2c_send_byte(0b01100000);
	i2c_send_stop();
	baby_temp_broken = i2c_timeout;
*/

	i2c_send_start();
	i2c_send_byte(0b10011110);  // Send TC75 ADDRESS (write)
	i2c_send_byte(0b00000001);  // Config address
	i2c_send_byte(0b01100000);
	i2c_send_stop();
	air_temp_broken = i2c_timeout;

	// This makes sure the first conversion is ready
	delay_ms(300);

//  music();
}

void main(void)
{
  init();

  incubator_main();
}


